/*******************************************************************
 * author   :  Charlies-ls
 * Date     :  2021-09-25
 * Copyright (c) 2021. artestplay@outlook.com. All Rights Reserved.
 *******************************************************************/


#include "ax_hashtable.h"
#include "ax_vector.h"
#include "ax_list.h"
#include "murmur.h"
#include "com.h"
#include "ax_printf.h"
#include <memory.h>
#include <inttypes.h>

struct ax_hashtable_entry
{
    void*  key;
    size_t key_size;
    void*  value;
    size_t value_size;
    ax_hashtable_entry *next;
    ax_list_node* list_node;  //for quick op
};

struct ax_hashtable
{
    HashFunc *hashfunc_x86_32;
    HashFunc *hashfunc_x86_128;
    HashFunc *hashfunc_x64_128;

    size_t      key_count;
    ax_vector*  array;
    ax_list*    entrys;       /* store all entrys only for iterator */
    size_t      collisions;
    uint32_t    flags;
    float       max_load_factor;
    float       current_load_factor;

};


ax_hashtable_entry *ax_hashtable_entry_create(uint32_t flags,
                                              const void *key,
                                              size_t key_size,
                                              const void *value,
                                              size_t value_size)
{
    ax_hashtable_entry *entry = ALLOC(ax_hashtable_entry);
    ax_check_and_return_zero(entry, "error");

    entry->key_size = key_size;
    if (flags & HT_KEY_CONST)
    {
        entry->key = (void*)key;
    }
    else
    {
        entry->key = ALLOCN(uint8_t, key_size);
        if(NULL == entry->key)
        {
            ax_debug("Failed to create ax_hashtable_entry\n");
            free(entry);
            return NULL;
        }
        memcpy(entry->key, key, key_size);
    }

    entry->value_size = value_size;
    if (flags & HT_VALUE_CONST)
    {
        entry->value = (void*)value;
    }
    else
    {
        entry->value = ALLOCN(uint8_t, value_size);
        if(NULL == entry->value)
        {
            ax_debug("Failed to create ax_hashtable_entry\n");
            free(entry->key);
            free(entry);
            return NULL;
        }
        memcpy(entry->value, value, value_size);
    }
    entry->next = NULL;
    return entry;
}

void ax_hashtable_entry_destory(ax_hashtable_entry *entry, uint32_t flags)
{
    if (!(flags & HT_KEY_CONST))
    {
        FREE(entry->key);
    }
    if (!(flags & HT_VALUE_CONST))
    {
        FREE(entry->value);
    }
    FREE(entry);
}

/**
 * @brief ax_hashtable_entry_key_compare
 * @param e1
 * @param e2
 * @return true if same
 */
bool ax_hashtable_entry_key_compare(ax_hashtable_entry *e1, ax_hashtable_entry *e2)
{
    uint8_t *k1 = e1->key;
    uint8_t *k2 = e2->key;

    if(e1->key_size != e2->key_size)
        return false;

    return memcmp(k1,k2,e1->key_size) == 0;
}

void ax_hashtable_entry_set_value(ax_hashtable_entry *entry, void *value, size_t value_size, uint32_t flags)
{
    if (!(flags & HT_VALUE_CONST))
    {
        if(entry->value)
            FREE(entry->value);

        entry->value = ALLOCN(uint8_t, value_size);
        ax_check_and_return(entry->value, "error");

        memcpy(entry->value, value, value_size);
    }
    else
    {
        entry->value = value;
    }
    entry->value_size = value_size;
}



ax_hashtable *ax_hashtable_create(AX_HT_FLAGS flags)
{
    ax_hashtable* table     = ALLOC(ax_hashtable);
    ax_check_and_return_zero(table, "error");

    table->hashfunc_x86_32  = MurmurHash3_x86_32;
    table->hashfunc_x86_128 = MurmurHash3_x86_128;
    table->hashfunc_x64_128 = MurmurHash3_x64_128;
    table->array            = ax_vector_create(128, 0);
    if(NULL == table->array)
    {
        FREE(table);
        return NULL;
    }
    table->entrys           = ax_list_create();

    table->key_count            = 0;
    table->collisions           = 0;
    table->flags                = flags;
    table->max_load_factor      = 0.1;
    table->current_load_factor  = 0.0;

    ax_vector_resize(table->array, 128);
    for(int i=0;i<ax_vector_size(table->array);i++)
    {
        ax_vector_set(table->array, i, NULL);
    }
    return table;
}

void ax_hashtable_destory(ax_hashtable *table)
{
    ax_check_and_return(table, "empty");

    ax_hashtable_entry *entry;
    ax_hashtable_entry *tmp;
    while(ax_vector_size(table->array))
    {
        entry = ax_vector_erase(table->array, 0);
        if(entry)
        {
            ax_hashtable_entry_destory(entry, table->flags);
        }
    }
    ax_vector_destory(table->array);
    ax_list_destory(table->entrys);
    FREE(table);
}

uint32_t g_seed = 2976579765;
uint32_t ax_hashtable_map_key_to_index(ax_hashtable *table, const void *key, size_t key_size)
{
    ax_check_and_return_zero(table && key, "error");
    uint32_t index;
    table->hashfunc_x86_32(key, key_size, g_seed, &index);
    index %= ax_vector_size(table->array);
    return index;
}

/*
 *   only change the array size for speed
 */
void ax_hashtable_resize(ax_hashtable *table, uint32_t new_size)
{
    ax_check_and_return(table, "error");
    ax_vector_resize(table->array, new_size);
}

void ax_hashtable_insert(ax_hashtable *table, const void *key, size_t key_size, const void *value, size_t value_size)
{
    ax_check_and_return(table && key, "error");

    ax_hashtable_entry *entry = ax_hashtable_entry_create(table->flags, key, key_size, value, value_size);
    ax_hashtable_entry*tmp;
    size_t index;
    bool find;

    entry->next = NULL;
    index       = ax_hashtable_map_key_to_index(table, entry->key, entry->key_size);

    if((index+1) > ax_vector_size(table->array))
    {
        ax_vector_resize(table->array, index+1); //grow the vector
    }

    tmp = (ax_hashtable_entry*)ax_vector_at(table->array, index);
    if(tmp == NULL)
    {
        /* the first element */
        ax_list_node* node = ax_list_push_back_data(table->entrys, entry);
        entry->list_node   = node;                    /* only for quick remove */
        ax_vector_set(table->array, index, entry);    /* put to bucket now     */
        table->key_count++;
        return;
    }

    while(tmp->next != NULL)
    {
        if(ax_hashtable_entry_key_compare(tmp, entry))
        {
            break;
        }
        else
        {
            tmp = tmp->next;
        }
    }

    if(tmp)
    {
        /* if same, only set value, and release entry */
        ax_hashtable_entry_set_value(tmp, entry->value, entry->value_size, table->flags);
        ax_hashtable_entry_destory(entry, table->flags);
        return;
    }
    else
    {
        /* add entry to tail */
        tmp->next = entry;
        table->collisions += 1;
        table->key_count ++;
        table->current_load_factor = (float)table->collisions / ax_vector_size(table->array);

        /* only for quick remove */
        ax_list_node* node = ax_list_push_back_data(table->entrys, entry);
        entry->list_node   = node;

        /* adjust the array size to avoid collisions */
        if(!(table->flags & HT_NO_AUTORESIZE) &&
            (table->current_load_factor > table->max_load_factor))
        {
            ax_hashtable_resize(table, ax_vector_size(table->array) * 2);
            table->current_load_factor = (float)table->collisions / ax_vector_size(table->array);
        }
    }
}

void* ax_hashtable_get(ax_hashtable *table, const char *key, size_t key_size, size_t *value_size)
{
    ax_check_and_return_zero(table && key, "empty");

    ax_hashtable_entry tmp;
    uint32_t index              = ax_hashtable_map_key_to_index(table, key, key_size);
    ax_hashtable_entry *entry   = ax_vector_at(table->array, index);

    tmp.key                     = (void*)key;
    tmp.key_size                = key_size;

    while(entry != NULL)
    {
        if(ax_hashtable_entry_key_compare(entry, &tmp))
        {
            if(value_size != NULL)
                *value_size = entry->value_size;
            return entry->value;
        }
        else
        {
            entry = entry->next;
        }
    }

    return NULL;
}

/**
 * @brief ax_hashtable_remove
 * @param table
 * @param key
 * @param key_size
 * @return true when delete ok, false delete nothing
 */
bool ax_hashtable_remove(ax_hashtable *table, const char *key, size_t key_size)
{
    ax_hashtable_entry tmp;
    size_t index                = ax_hashtable_map_key_to_index(table, key, key_size);
    ax_hashtable_entry *entry   = ax_vector_at(table->array, index);
    ax_hashtable_entry *prev    = NULL;
    tmp.key                     = (void*)key;
    tmp.key_size                = key_size;

    while(entry != NULL)
    {
        if(ax_hashtable_entry_key_compare(entry, &tmp))
        {
            if(NULL == prev)
                ax_vector_set(table->array, index, entry->next);
            else
                prev->next = entry->next;

            table->key_count--;

            if(prev != NULL)
                table->collisions--;

            if(entry->list_node)
                ax_list_del(table->entrys, entry->list_node);

            ax_hashtable_entry_destory(entry, table->flags);
            return true;
        }
        else
        {
            prev = entry;
            entry = entry->next;
        }
    }
    return NULL;
}

bool ax_hashtable_contains(ax_hashtable *table, const char *key, size_t key_size)
{
    ax_hashtable_entry tmp;
    unsigned int index        = ax_hashtable_map_key_to_index(table, key, key_size);
    ax_hashtable_entry *entry = ax_vector_at(table->array, index);
    tmp.key                   = (void*)key;
    tmp.key_size              = key_size;

    while(entry != NULL)
    {
        if(ax_hashtable_entry_key_compare(entry, &tmp))
            return true;
        else
            entry = entry->next;
    }
    return false;
}

uint32_t ax_hashtable_size(ax_hashtable *table)
{
    ax_check_and_return_zero(table, "empty");
    return table->key_count;
}

void* ax_hashtable_entry_get_key(void* entry)
{
    ax_list_node* node = (ax_list_node*)entry;
    ax_check_and_return_zero(node && node->data, NULL);
    return ((ax_hashtable_entry*)node->data)->key;
}

void* ax_hashtable_entry_get_value(void* entry)
{
    ax_list_node* node = (ax_list_node*)entry;
    ax_check_and_return_zero(node && node->data, NULL);
    return ((ax_hashtable_entry*)node->data)->value;
}

void* ax_hashtable_get_head(ax_hashtable* table)
{
    ax_check_and_return_zero(table, "empty");
    return ax_list_front(table->entrys);
}

/**
 * @brief ax_hashtable_clear, only free entrys
 * @param table
 */
void ax_hashtable_clear(ax_hashtable *table)
{
    ax_check_and_return(table, "empty");

    /* free contents */
    ax_list_foreach(table->entrys, node)
    {
        ax_hashtable_entry_destory((ax_hashtable_entry*)node->data, table->flags);
    }
    ax_list_clear(table->entrys);
    ax_vector_clear(table->array);

    table->key_count            = 0;
    table->collisions           = 0;
    table->current_load_factor  = 0.0;
}
